home *** CD-ROM | disk | FTP | other *** search
- /*
- File: DTS_SCSI_Driver.c
-
-
-
-
- Unfortunately, no matter how long awaited, it's still not done. In fact, this
- isn't even a release- this is just an image of the code taken in the middle of
- development.
-
- THIS CODE DOES NOT WORK AS A WHOLE. MUCH OF IT IS BUGGY AND / OR INCOMPLETE.
- YOU WOULD HAVE TO BE ABSOLUTELY INSANE TO USE ANY OF THIS CODE IN YOUR
- PROJECT WITHOUT EXTENSIVE THOUGHT, DEBUGGING AND TESTING.
-
-
-
-
-
- Contains: main driver routines
-
- Written by: Craig Prouse
-
- Copyright: © 1991-1992 by Apple Computer, Inc., all rights reserved.
-
- Change History (most recent first):
-
- <9> 4/24/92 tmd Code review stuff; lots of nitpicking on comments & format
- <8> 3/6/92 chp fix more bugs: eliminate drive queue entry when closing driver;
- clean up comments and generally pick nits
- <7> 11/26/91 chp fix bugs: wait until after initializing dCtlStorage to set up a
- pointer to driver variables for the Open routine, allocate
- dCtlStorage low in the system heap and *lock* it there, skip
- drive validation on accRun calls, clear the correct bit of
- dCtlFlags to cancel accRuns (was setting the correct bit in the
- wrong byte), and remove debugger asserts for unimplemented
- status and control calls (we can expect these)
- <6> 11/25/91 chp roll in changes from old DrvrUtils module, add support for
- volume validation, break out a couple of common code segments
- into separate functions for ease of maintenance, and make the
- accRun/eject support more comprehensible
- <5> 10/25/91 chp DriveIcon was declared as a function for convenience. Actually,
- it's a block of constant data. Redeclare it more properly as a
- generic external entity.
- <4> 10/16/91 chp complete implementations of the accRun and eject Control calls,
- and complete implementation of the Open routine by adding code
- to install us in the drive queue
- <3> 10/16/91 chp update to reflect changes in our headers
- <2> 10/15/91 chp update header contents
- <1> 10/15/91 chp first checked in
-
- To Do:
- */
-
- #define __FILE_NUMBER__ 0x1000 //*** Change to a kconstant.
-
- #include <Traps.h>
- #include <SysEqu.h>
- #include <Traps.h>
- #include <Errors.h>
- #include <Desk.h>
- #include <Memory.h>
- #include <OSEvents.h>
- #include <Files.h>
- #include <Devices.h>
- #include "DTS_SCSI_Debug.h"
- #include "DTS_SCSI_Driver.h"
- #include "DTS_SCSI_IO.h"
-
- //
- // Our icon, included by DTS_SCSI_Driver.a as a block of constant data
- //
- extern short DriveIcon[];
-
- //
- // What kind of I/O request is this?
- // It's either synchronous, asynchronous, or immediate, and we can
- // tell by checking the bits in the trap word that got us here (the
- // Device Manager kindly copied the trap word into the parameter block
- // as part of its dispatching).
- //
- typedef enum TIOReqType { kReqSync, kReqAsync, kReqImmed } TIOReqType;
- TIOReqType IORequestType(ParmBlkPtr pb) {
- unsigned short trap = pb->ioParam.ioTrap;
- TIOReqType typeOfIOReq;
-
- enum { // Bits set in the trap word
- noQueueBit = 9, // IMMED bit
- asyncTrpBit = 10 // ASYNC bit
- };
-
- if (trap & (1 << noQueueBit))
- typeOfIOReq = kReqImmed;
- else if (trap & (1 << asyncTrpBit))
- typeOfIOReq = kReqAsync;
- else
- typeOfIOReq = kReqSync;
-
- return typeOfIOReq;
- }
-
-
- //
- // Which kind of Prime operation is this?
- // We figure it out from the trap word in the parameter block;
- // we mask off the bits that complicate trap words, and we
- // should be left with _Read or _Write.
- //
- typedef enum { kPrimeRead, kPrimeWrite } EPrimeType;
-
- EPrimeType PrimeType(ParmBlkPtr pb) {
- const unsigned short osTrapFlagsMask = 0xF0FF;
- unsigned short trap = pb->ioParam.ioTrap & osTrapFlagsMask;
- EPrimeType typeOfPrime;
-
- if (trap == _Read & osTrapFlagsMask)
- typeOfPrime = kPrimeRead;
- else if (trap == _Write & osTrapFlagsMask)
- typeOfPrime = kPrimeWrite;
- else
- assert(false, "DTS_SCSI PrimeType: trap not read or write");
-
- return typeOfPrime;
- }
-
-
- //
- // The SCSI Manager supports Blind transfers, but they're unreliable on
- // the unpatched Macintosh Plus ROM (once the Macintosh Plus boots, they're
- // safe; they're also always safe on all other Macintoshes with SCSI).
- //
- Boolean BlindIsSafe() { //*** Need a better way to check if blind is safe
- const unsigned short kMacPlusROMVersion = 0x0075;
-
- //
- // We check for Macintosh-Plus-ness by looking at the ROM version (8 bytes into
- // the ROM) -- note that this is not a good way to see what a machine's ROM version
- // is. Gestalt would be better, but the whole point here is that we use this routine
- // before the System has been opened, and Gestalt isn't around yet on the Macintosh
- // Plus.
- //
- // Once we know it's a Plus, we see if _SCSIDispatch has been patched by getting
- // its address and seeing if it's above the start of the ROM. If so, we're not patched
- // yet.
- //
- if (*(short *) (*(Ptr *) ROMBase + 8) == kMacPlusROMVersion) {
- if (NGetTrapAddress(_SCSIDispatch, OSTrap) >= *(long *) ROMBase) {
- return false; // don't do blind transfers!
- }
- }
-
- return true; // blind transfers are OK.
- }
-
-
- //
- // If this drive number is ours, return true.
- // Otherwise, return false.
- //
- Boolean IsValidDrive(TDriveVars *vars, short theDrive) {
- //
- // Is it our drive? Compare this drive number with the one in our
- // drive queue element.
- //
- return (vars->driveQElem.elem.dQDrive == theDrive);
- }
-
-
- //
- // If this drive number is valid, return the size of our drive, in blocks.
- // Otherwise return 0.
- //
- unsigned long DriveSize(TDriveVars *vars, short theDrive) {
- //
- // First, make sure this is our drive
- //
- if (!IsValidDrive(vars, theDrive))
- return 0;
-
- // It's our drive. Now, about that size...
- // The size (in blocks) of a given drive is stored in the drive queue element
- // fields dqDrv and dqDrv2. Originally, there wasn't a dqDrv2, and we were limited
- // to drives with 32M or less (I feel old just telling you this… "When I was your age…"),
- // so the extra field was added. Here, we put 'em together and return it as one big number.
- return vars->driveQElem.elem.dQDrvSz | (vars->driveQElem.elem.dQDrvSz2 << 16);
- }
-
-
- //
- // Is this boot time?
- //
- Boolean IsBootTime() {
- //
- // One characteristic of boot time is that SysEvtMask is zero
- // *** This is the '90s - there's got to be a better way to check for this!
- //
- return *(short *) SysEvtMask == 0;
- }
-
- //
- // Open our driver. This routine is called by the system when the driver is
- // installed, either at boot time or by the Formatter when the drive is newly
- // initialized.
- //
- // Our job at "open" time is to set up a drive queue element (along with some
- // other state information) for our one-and-only drive. We don't need to mount
- // the drive at boot time because the system knows enough to try to boot from it;
- // however, if that try doesn't succeed (because there's no system folder, etc.),
- // we'll need to sneak around to get it mounted as soon as the system finishes
- // starting up: this is what the "tickleFlag" state variable is for.
- //
- OSErr OpenFunc(ParmBlkPtr pb, DCtlPtr dce) {
- #pragma unused (pb)
- TDriveVars *vars;
- DrvQEl *aDrive;
- short tryDriveNum;
-
- //
- // Allocate storage for our state. We lock it down and leave it that
- // way, because it contains a drive queue element (which can never move
- // once we've added it to the drive queue, which we're about to do.)
- //
- ReserveMemSys(sizeof(TDriveVars));
- dce->dCtlStorage = NewHandleSysClear(sizeof(TDriveVars));
- assert(dce->dCtlStorage, "DTS_SCSI OpenFunc: unable to allocate dCtlStorage");
-
- // If we couldn't get the space, just return the error code (leaving dCtlStorage NULL)
- if (dce->dCtlStorage == NULL)
- return MemError();
-
- // We got the storage. Remember where it is, and lock it down.
- HLock(dce->dCtlStorage);
- vars = *(TDriveVars **) dce->dCtlStorage;
-
- //
- // Now we need to find a drive number for our drive. We're going to look
- // through the drive queue, and use the first available number greater than
- // four (the lower numbers are reserved for the floppy drives and the old HD20).
- //
- tryDriveNum = 5;
- aDrive = (DrvQEl *) GetDrvQHdr()->qHead; // get the head of the drive queue
- while (aDrive != NULL) {
- if (aDrive->dQDrive == tryDriveNum) {
- // Oops, someone's already using this drive number.
- tryDriveNum++; // try next drive number
- aDrive = (DrvQEl *) GetDrvQHdr()->qHead; // start again at the beginning of the queue
- } else {
- // Good so far--check against the next drive in the queue.
- aDrive = (DrvQEl *) aDrive->qLink;
- }
- }
- // We fall out of the loop when we've made it all the way to the end of the queue without
- // finding an existing drive queue element using our number. So, at this point, tryDriveNum
- // contains a unique drive number for us to use.
- vars->driveQElem.elem.dQDrive = tryDriveNum; // Remember it in our drive state
-
- // Can we do blind transfers? We check here in case we're running on a MacPlus
- // where the code in ROM won't do them right; we'll check again when we get an
- // accRun call (once the Finder's up) - at that point, even the Plus has them
- // working correctly.
- vars->blindOK = BlindIsSafe();
-
- //
- // We've got a drive number - fill in the rest of our drive queue entry (it was zeroed when
- // we allocated it, so we just have to deal with the nonzero stuff), then tell the
- // system about it with AddDrive.
- //
- vars->driveQElem.flags[1] = noEjectFlag; // nonejectable media flag
- vars->driveQElem.elem.qType = 1; // drive size stored in both dQDrvSz and dQDrvSz2
- AddDrive(dce->dCtlRefNum, tryDriveNum, &vars->driveQElem.elem);
-
- return noErr; // "Yes! We're Open"
- }
-
-
- //
- // Prime means "Read" and "Write", which is what we're s'posed to do here.
- // We've got a low-level routine that takes care of most of the tough stuff,
- // so all we have to do is verify that the drive number and the size of the
- // request are legit.
- //
- OSErr PrimeFunc (ParmBlkPtr pb, DCtlPtr dce)
- {
- OSErr result;
- unsigned long blockSize, firstLogicalBlock, firstPhysicalBlock, nBlocks;
- unsigned long partitionSize;
- TDriveVars *vars = *(TDriveVars **) dce->dCtlStorage;
-
- // How big's our drive?
- partitionSize = DriveSize(vars, pb->ioParam.ioVRefNum);
-
- // If the size we got isn't positive, this ain't our drive. Return an error
- if (partitionSize <= 0)
- return nsvErr;
-
- //
- // The drive number is good. Can we satisfy the request? We start with the
- // block offset to the start of the partition, and offset it by the block
- // address of the start of the request (which came to us as a byte count;
- // a quick divide turns this into a block count).
- //
- blockSize = vars->driveBlockSize;
- nBlocks = pb->ioParam.ioReqCount / blockSize; // how many blocks?
- firstLogicalBlock = dce->dCtlPosition / blockSize; // first requested block in our partition
- firstPhysicalBlock = vars->partitionOffset + firstLogicalBlock; // offset to physical block
-
- // Theoretically, we only get called with requests that start at a block boundary,
- // and ask for an even number of blocks. Just to be safe, we check this.
- assert(dce->dCtlPosition % blockSize == 0,"DTS_SCSI PrimeFunc: Starting at weird offset");
- assertv(pb->ioParam.ioReqCount % blockSize == 0,pb->ioParam.ioReqCount % blockSize);
-
- //
- // Sanity check: the request must be nonempty and within the boundaries of
- // our partition. If it's sane, do the transfer.
- //
- if ((nBlocks > 0) && (firstLogicalBlock + nBlocks <= partitionSize)) {
- // It's a valid request. Call the low-level routine to do it.
- result = SCSIPrime(pb->ioParam.ioBuffer, vars->driveID, 0, firstPhysicalBlock,
- &nBlocks, blockSize, PrimeType(pb) == kPrimeWrite, vars->blindOK, kIOTimeout);
- assert(result == noErr, "DTS_SCSI PrimeFunc: SCSIPrime failed");
- } else {
- assert(false, "DTS_SCSI PrimeFunc: requested block(s) not in partition");
- nBlocks = 0; // no blocks transferred
- result = paramErr;
- }
-
- //
- // Return the actual number of bytes transferred, and move the current position by
- // this amount.
- //
- pb->ioParam.ioActCount = nBlocks * blockSize;
- assert(pb->ioParam.ioActCount == pb->ioParam.ioReqCount,"DTS_SCSI PrimeFunc: Couldn't read entire request");
- dce->dCtlPosition += pb->ioParam.ioActCount;
-
- // a result must be negative, so coerce a positive SCSI status byte to an ioErr
- // *** spruce this up with a comment.
- return result > 0 ? ioErr : result;
- }
-
-
- //
- // Control calls to the driver get vectored to here.
- //
- OSErr ControlFunc(ParmBlkPtr pb, DCtlPtr dce) {
- TDriveVars *vars;
- OSErr result;
-
- //
- // Make sure we got our storage; return an error if we didn't
- //
- if (dce->dCtlStorage == NULL)
- return notOpenErr;
- vars = *(TDriveVars **) dce->dCtlStorage;
-
- switch (pb->cntrlParam.csCode) {
- case killCode:
- //
- // We ignore KillIO -- we're completely synchronous.
- // *** we note it during debugging, just for fun.
- assert(false,"ControlFunc: KillIO called, by the way.");
- result = noErr;
- break;
-
- case verifyCode:
- case formatCode:
- //
- // We ignore Verify and Format; they both return "no problem" as long
- // as the specified drive is ours. (The only way to format a drive is
- // from our formatting application; when the user chooses Erase Disk
- // from the Finder, the driver doesn't need to do anything; DIZero()
- // will "erase" the disk.)
- //
- result = (IsValidDrive(vars, pb->ioParam.ioVRefNum)) ? noErr : nsvErr;
- break;
-
- case ejectCode:
- if (!IsValidDrive(vars, pb->ioParam.ioVRefNum)) {
- result = nsvErr;
- } else {
- //
- // The eject call for a fixed hard disk is a little bit unusual. Since
- // it doesn't make sense to eject a hard disk, we just behave stubbornly
- // and post disk-insert events for ourselves in response.
- //
- // The exception is at boot time, when we may receive an eject call if we
- // fail to become the boot volume. In this case, we must wait until our
- // first accRun call to post the disk insert event.
- //
- // No matter what, we return an error.
- //
- result = controlErr; // we're going to return controlErr either way.
- if (IsBootTime()) {
- //
- // The tickleFlag may be set false by a prime call during the boot process.
- // If, for instance, this volume has no system folder, the boot code may
- // first read from it then try to eject it like a floppy. Setting tickleFlag
- // true reverses the effects of the prime routine's negation of tickleFlag.
- //
- vars->tickleFlag = true;
- } else {
- // We're not at boot time. Reinsert the drive.
- // *** I'm not sure that we still need to do this. Dragging the volume
- // to the trash needs to work!
- PostEvent(diskEvt, vars->driveQElem.elem.dQDrive);
- }
- }
- break;
-
- case mediaIconCode:
- case deviceIconCode:
- //
- // If the drive's valid, return a pointer to our icon & mask in [csParam].
- // The Finder will use this icon for our drive on the desktop. Note that
- // we can only provide a black-and-white icon; only the user can customize
- // the icon and make it colored.
- // The deviceIconCode selector is used for the "physical" icon; this is used
- // by DIBadMount to give an indication of where the disk is (or, under System
- // 6, the Finder will use it if the "Use Physical Icon" bit is set in the
- // LAYO resource.)
- //
- if (IsValidDrive(vars, pb->ioParam.ioVRefNum)) {
- *(long *) pb->cntrlParam.csParam = DriveIcon;
- result = noErr;
- } else
- result = nsvErr;
- break;
-
- case accRun:
- //
- // If we aren't the boot device we probably got "ejected" and need this
- // tickle to reassert ourselves and get mounted on the desktop.
- //
- if (vars->tickleFlag && vars->partitionOffset != 0)
- PostEvent(diskEvt, vars->driveQElem.elem.dQDrive);
-
- //
- // Also, we should check again to see if blind transfers are safe.
- // Since last time we checked, ROM patches have been loaded.
- //
- vars->blindOK = BlindIsSafe();
-
- //
- // Turn off future accRun calls - we don't need 'em anymore.
- //
- dce->dCtlFlags &= ~(dNeedTime << 8); // cancel further accRun calls (in high byte of flags)
- vars->tickleFlag = false; // clear our insertion indication
-
- result = noErr;
- break;
-
- case goodbye:
- // we don't need a goodbye kiss, but if we did, we'd act on it here.
- default:
- result = controlErr; // "undefined csCode"
- break;
- }
-
- return result;
- }
-
-
- //
- // Status calls come here.
- //
- OSErr StatusFunc(ParmBlkPtr pb, DCtlPtr dce)
- {
- TDriveVars *vars = *(TDriveVars **) dce->dCtlStorage;
- OSErr result = nsvErr;
-
- if (IsValidDrive(vars, pb->cntrlParam.ioVRefNum))
- switch (pb->cntrlParam.csCode) {
- default:
- result = statusErr; // undefined csCode
- break;
- }
-
- return result;
- }
-
- OSErr CloseFunc (ParmBlkPtr pb, DCtlPtr dce)
- {
- #pragma unused (pb)
-
- TDriveVars *vars = *(TDriveVars **) dce->dCtlStorage;
- if (vars != NULL) {
- // Dequeue our drive queue element
- Dequeue((QElemPtr) &vars->driveQElem, GetDrvQHdr());
-
- // Dispose of our storage, and forget we ever had it.
- DisposHandle((Handle) vars);
- dce->dCtlStorage = NULL;
- assert(MemError() == noErr, "DTS_SCSI CloseFunc: error disposing dCtlStorage");
- }
- return noErr;
- }
-
-